package org.springframework.security.web.session;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.session.SessionAuthenticationException;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;

import java.io.IOException;

/**
 * @author Dillon
 * @date 2024/7/14
 * @slogan 致敬大师 致敬未来的你
 * @desc 记住我过滤器配置类
 */
public class SessionManagementFilter extends GenericFilterBean {

	/**
	 * 过滤器已执行标记
	 */
	static final String FILTER_APPLIED = "__spring_security_session_mgmt_filter_applied";

	/**
	 * 上下文缓存策略类
	 */
	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
		.getContextHolderStrategy();

	/**
	 * 上下文持久化实现类
	 */
	private final SecurityContextRepository securityContextRepository;

	/**
	 * 设置 session 认证策略类 CompositeSessionAuthenticationStrategy
	 */
	private final SessionAuthenticationStrategy sessionAuthenticationStrategy;

	/**
	 * 身份认证解析处理器
	 */
	private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

	/**
	 * 无效session策略类
	 */
	private InvalidSessionStrategy invalidSessionStrategy = null;

	/**
	 * 认证失败处理器 默认执行请求跳转
	 */
	private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();

	public SessionManagementFilter(SecurityContextRepository securityContextRepository) {
		this(securityContextRepository, new SessionFixationProtectionStrategy());
	}

	public SessionManagementFilter(SecurityContextRepository securityContextRepository,
			SessionAuthenticationStrategy sessionStrategy) {
		Assert.notNull(securityContextRepository, "SecurityContextRepository cannot be null");
		Assert.notNull(sessionStrategy, "SessionAuthenticationStrategy cannot be null");
		this.securityContextRepository = securityContextRepository;
		this.sessionAuthenticationStrategy = sessionStrategy;
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
	}

	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		// 一个请求调用一次临界值判断
		if (request.getAttribute(FILTER_APPLIED) != null) {
			chain.doFilter(request, response);
			return;
		}
		// 设置请求调用过当前过滤器
		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
		// 如果当前认证对象执行过持久化，则从持久化中获取当前认证成功上下文
		if (!this.securityContextRepository.containsContext(request)) {
			Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
			// 如果是常规请求 非匿名 已认证成功
			if (this.trustResolver.isAuthenticated(authentication)) {
				// The user has been authenticated during the current request, so call the
				// session strategy
				try {
					// 执行当前session认证策略 配置并行登录最大账户数等 CSRF攻击过滤等
					this.sessionAuthenticationStrategy.onAuthentication(authentication, request, response);
				}
				catch (SessionAuthenticationException ex) {
					// 认证失败 清空缓存 执行自定义失败回调
					this.logger.debug("SessionAuthenticationStrategy rejected the authentication object", ex);
					this.securityContextHolderStrategy.clearContext();
					this.failureHandler.onAuthenticationFailure(request, response, ex);
					return;
				}
				// 认证成功 执行持久化
				this.securityContextRepository.saveContext(this.securityContextHolderStrategy.getContext(), request,
						response);
			}
			else {
				// 非常规请求 匿名 等 如果session过期等判断是否执行无效会话策略
				if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {
					if (this.logger.isDebugEnabled()) {
						this.logger.debug(LogMessage.format("Request requested invalid session id %s",
								request.getRequestedSessionId()));
					}
					if (this.invalidSessionStrategy != null) {
						this.invalidSessionStrategy.onInvalidSessionDetected(request, response);
						return;
					}
				}
			}
		}
		chain.doFilter(request, response);
	}

	/**
	 * 设置自定义session会话无效策略类
	 * @param invalidSessionStrategy 会话无效策略类
	 */
	public void setInvalidSessionStrategy(InvalidSessionStrategy invalidSessionStrategy) {
		this.invalidSessionStrategy = invalidSessionStrategy;
	}

	/**
	 * The handler which will be invoked if the <tt>AuthenticatedSessionStrategy</tt>
	 * raises a <tt>SessionAuthenticationException</tt>, indicating that the user is not
	 * allowed to be authenticated for this session (typically because they already have
	 * too many sessions open).
	 *
	 */
	public void setAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler) {
		Assert.notNull(failureHandler, "failureHandler cannot be null");
		this.failureHandler = failureHandler;
	}

	/**
	 * Sets the {@link AuthenticationTrustResolver} to be used. The default is
	 * {@link AuthenticationTrustResolverImpl}.
	 * @param trustResolver the {@link AuthenticationTrustResolver} to use. Cannot be
	 * null.
	 */
	public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
		Assert.notNull(trustResolver, "trustResolver cannot be null");
		this.trustResolver = trustResolver;
	}

	/**
	 * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use
	 * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}.
	 *
	 * @since 5.8
	 */
	public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
		Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
		this.securityContextHolderStrategy = securityContextHolderStrategy;
	}

}
